'use strict'

const assert = require('node:assert/strict')

const { expect } = require('chai')
const { afterEach, beforeEach, describe, it } = require('mocha')
const sinon = require('sinon')

const vulnerabilityFormatter = require('../../../../src/appsec/iast/vulnerabilities-formatter')
const sensitiveHandler =
  require('../../../../src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler')

const { suite } = require('./resources/evidence-redaction-suite.json')
const excludedVulnerabilityTypes = ['XSS', 'EMAIL_HTML_INJECTION', 'HEADER_INJECTION']
const excludedTests = [
  'Query with single quoted string literal and null source', // does not apply
  'Redacted source that needs to be truncated', // not implemented yet
  'CODE_INJECTION - Tainted range based redaction - with null source ', // does not apply
  'TEMPLATE_INJECTION - Tainted range based redaction - with null source ', // does not apply
  'UNTRUSTED_DESERIALIZATION - Tainted range based redaction - with null source ' // does not apply
]

function doTest (testCase, parameters) {
  let { description, input, expected } = testCase
  if (parameters) {
    parameters.forEach(parameter => {
      description = description.split(parameter.name).join(parameter.value)
      input = JSON.parse(JSON.stringify(input).split(parameter.name).join(parameter.value))
      expected = JSON.parse(JSON.stringify(expected).split(parameter.name).join(parameter.value))
    })
  }

  if (expected.vulnerabilities?.length && excludedVulnerabilityTypes.includes(expected.vulnerabilities[0].type)) {
    return
  }

  if (excludedTests.includes(description)) {
    return
  }

  it(description, () => {
    const testInput = input.map(i => (
      {
        ...i,
        location: {}
      }
    ))
    const formattedVulnerabilities = vulnerabilityFormatter.toJson(testInput)
    const vulnerabilitiesToCompare = formattedVulnerabilities.vulnerabilities.map(v => (
      {
        type: v.type,
        evidence: v.evidence
      }
    ))
    assert.deepStrictEqual(vulnerabilitiesToCompare, expected.vulnerabilities, 'Vulnerabilities does not match')

    if (expected.sources) {
      assert.deepStrictEqual(formattedVulnerabilities.sources, expected.sources, 'Sources does not match')
    }
  })
}

function extractTestParameters (testCase) {
  let testsParameters = []
  for (const name in testCase.parameters) {
    const params = []
    testCase.parameters[name].forEach(value => {
      params.push({ name, value })
    })

    if (testsParameters.length === 0) {
      testsParameters.push(...params.map(p => [p]))
    } else {
      const newTestsParameter = []
      params.forEach(param => {
        testsParameters.forEach(testParameters => {
          newTestsParameter.push([param, ...testParameters])
        })
      })
      testsParameters = newTestsParameter
    }
  }
  return testsParameters
}

describe('Vulnerability formatter', () => {
  describe('Vulnerability redaction', () => {
    suite.filter(testCase => testCase.type === 'VULNERABILITIES')
      .forEach((testCase) => {
        if (!testCase.parameters) {
          doTest(testCase)
        } else {
          const testsParameters = extractTestParameters(testCase)
          testsParameters.forEach(testParameters => {
            doTest(testCase, testParameters)
          })
        }
      })
  })

  describe('toJson', () => {
    it('should format vulnerability correctly', () => {
      const vulnerabilities = [{
        type: 'test-vulnerability',
        hash: 123456,
        evidence: {
          value: 'payload'
        },
        location: {
          path: 'path',
          line: 42,
          stackId: '1'
        }
      }]

      const result = vulnerabilityFormatter.toJson(vulnerabilities)

      assert.deepStrictEqual(result, {
        sources: [],
        vulnerabilities: [{
          type: 'test-vulnerability',
          hash: 123456,
          evidence: {
            value: 'payload'
          },
          location: {
            path: 'path',
            line: 42,
            stackId: '1'
          }
        }]
      })
    })
  })

  describe('Custom redaction patterns', () => {
    beforeEach(() => {
      sinon.stub(sensitiveHandler, 'setRedactionPatterns')
    })

    afterEach(() => {
      sinon.restore()
    })

    it('should set custom redaction patterns', () => {
      vulnerabilityFormatter.setRedactVulnerabilities(true, 'customNamePattern', 'customValuePattern')

      expect(sensitiveHandler.setRedactionPatterns)
        .to.have.been.calledOnceWithExactly('customNamePattern', 'customValuePattern')
    })
  })
})
